// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package tar

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"time"
)

// hasNUL报告NUL字符是否存在于s中。
func hasNUL(s string) bool {
	return strings.Contains(s, "\x00")
}

// isASCII报告输入是否为ASCII C样式字符串。
func isASCII(s string) bool {
	for _, c := range s {
		if c >= 0x80 || c == 0x00 {
			return false
		}
	}
	return true
}

// toASCII将输入转换为ASCII C样式字符串。
// 这是一个尽最大努力的转换，因此会删除无效字符。
func toASCII(s string) string {
	if isASCII(s) {
		return s
	}
	b := make([]byte, 0, len(s))
	for _, c := range s {
		if c < 0x80 && c != 0x00 {
			b = append(b, byte(c))
		}
	}
	return string(b)
}

type parser struct {
	err error // 最后一个错误
}

type formatter struct {
	err error // 最后一个错误
}

// parseString将字节解析为以NUL结尾的C样式字符串。
// 如果未找到NUL字节，则整个切片将作为字符串返回。
func (*parser) parseString(b []byte) string {
	if i := bytes.IndexByte(b, 0); i >= 0 {
		return string(b[:i])
	}
	return string(b)
}

// 格式字符串将s复制到b中，如果可能，NUL终止。
func (f *formatter) formatString(b []byte, s string) {
	if len(s) > len(b) {
		f.err = ErrFieldTooLong
	}
	copy(b, s)
	if len(s) < len(b) {
		b[len(s)] = 0
	}

	// 一些错误阅读器将V7路径字段中带有尾随斜杠的常规文件视为目录，即使在其他地方（例如，通过PAX record）记录的完整路径
	// 不包含尾随斜杠。
	if len(s) > len(b) && b[len(b)-1] == '/' {
		n := len(strings.TrimRight(s[:len(b)], "/"))
		b[n] = 0 // 用NUL终止符替换尾随斜杠
	}
}

// fitsInBase256报告是否可以使用base-256 
// 编码将x编码为n字节。与八进制编码不同，base-256编码不要求
// 字符串以NUL字符结尾。因此，所有n字节都可用于输出。
// 
// 如果在二进制模式下运行，则假定为严格的GNU二进制模式；这意味着第一个字节只能是0x80或0xff。因此，第一个字节是
// 相当于2的补码形式的符号位。
func fitsInBase256(n int, x int64) bool {
	binBits := uint(n-1) * 8
	return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
}

// parseNumeric将输入解析为以base-256或八进制编码。
// 此函数可能返回负数。
// 如果解析失败或出现整数溢出，将设置err。
func (p *parser) parseNumeric(b []byte) int64 {
	// 首先检查base-256（二进制）格式。
	// 如果设置了第一位，则所有后续位构成一个2的
	// 以大端字节顺序补码编码的数字。
	if len(b) > 0 && b[0]&0x80 != 0 {
		// 处理负数依赖于以下标识：
		// /-a-1==^a 
		// 
		// 如果数字为负数，我们使用反转掩码反转
		// 数据字节，并将该值视为无符号数。
		var inv byte // 0x00如果为正或零，0xff如果为负
		if b[0]&0x40 != 0 {
			inv = 0xff
		}

		var x uint64
		for i, c := range b {
			c ^= inv // 仅当inv为0xff时才反转c，否则不执行任何操作
			if i == 0 {
				c &= 0x7f // 忽略第一字节中的信号位
			}
			if (x >> 56) > 0 {
				p.err = ErrHeader // 整数溢出
				return 0
			}
			x = x<<8 | uint64(c)
		}
		if (x >> 63) > 0 {
			p.err = ErrHeader // 整数溢出
			return 0
		}
		if inv == 0xff {
			return ^int64(x)
		}
		return int64(x)
	}

	// 正常情况下为8进制（八进制）格式。
	return p.parseOctal(b)
}

// formatNumeric尽可能使用base-8（八进制）编码将x编码为b。
// 否则它将尝试使用base-256（二进制）编码。
func (f *formatter) formatNumeric(b []byte, x int64) {
	if fitsInOctal(len(b), x) {
		f.formatOctal(b, x)
		return
	}

	if fitsInBase256(len(b), x) {
		for i := len(b) - 1; i >= 0; i-- {
			b[i] = byte(x)
			x >>= 8
		}
		b[0] |= 0x80 // 最高位表示二进制格式
		return
	}

	f.formatOctal(b, 0) // 最后，只需写零
	f.err = ErrFieldTooLong
}

func (p *parser) parseOctal(b []byte) int64 {
	// 因为未使用的字段都用NUL填充，我们需要
	// 跳过前导NUL。字段也可以用
	// 空格或NUL填充。
	// 所以我们删除了
	// 的前导和尾随数字和空格。
	b = bytes.Trim(b, " \x00")

	if len(b) == 0 {
		return 0
	}
	x, perr := strconv.ParseUint(p.parseString(b), 8, 64)
	if perr != nil {
		p.err = ErrHeader
	}
	return int64(x)
}

func (f *formatter) formatOctal(b []byte, x int64) {
	if !fitsInOctal(len(b), x) {
		x = 0 // 最后，只需写零
		f.err = ErrFieldTooLong
	}

	s := strconv.FormatInt(x, 8)
	// 添加前导零，但为NUL留出空间。
	if n := len(b) - len(s) - 1; n > 0 {
		s = strings.Repeat("0", n) + s
	}
	f.formatString(b, s)
}

// fitsInOctal报告整数x是否适合n字节长的字段
// 使用带适当NUL终止符的八进制编码。
func fitsInOctal(n int, x int64) bool {
	octBits := uint(n-1) * 3
	return x >= 0 && (n >= 22 || x < 1<<octBits)
}

// parsePAXTime采用PAX 
// 规范中描述的格式为%d.%d的字符串。注意，这个实现允许使用负时间戳
// 这是PAX规范允许的，但并不总是可移植的。
func parsePAXTime(s string) (time.Time, error) {
	const maxNanoSecondDigits = 9

	// 将字符串拆分为秒和亚秒部分。
	ss, sn, _ := strings.Cut(s, ".")

	// 解析秒数。
	secs, err := strconv.ParseInt(ss, 10, 64)
	if err != nil {
		return time.Time{}, ErrHeader
	}
	if len(sn) == 0 {
		return time.Unix(secs, 0), nil // 没有亚秒值
	}

	// 解析纳秒。
	if strings.Trim(sn, "0123456789") != "" {
		return time.Time{}, ErrHeader
	}
	if len(sn) < maxNanoSecondDigits {
		sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // 右键
	} else {
		sn = sn[:maxNanoSecondDigits] // 右截断
	}
	nsecs, _ := strconv.ParseInt(sn, 10, 64) // 必须成功
	if len(ss) > 0 && ss[0] == '-' {
		return time.Unix(secs, -1*nsecs), nil // 负校正
	}
	return time.Unix(secs, nsecs), nil
}

// formatPAXTime将ts转换为格式为%d的时间。%d，如
// PAX规范所述。此函数可以使用负时间戳。
func formatPAXTime(ts time.Time) (s string) {
	secs, nsecs := ts.Unix(), ts.Nanosecond()
	if nsecs == 0 {
		return strconv.FormatInt(secs, 10)
	}

	// 如果秒数为负数，则执行校正。
	sign := ""
	if secs < 0 {
		sign = "-"             // 记住符号
		secs = -(secs + 1)     // 为秒添加一秒
		nsecs = -(nsecs - 1e9) // 从nsecs中去掉这一秒
	}
	return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
}

// parsePAXRecord将输入的PAX记录字符串解析为一个键值对。
// 如果解析成功，它将切掉当前读取的记录，并将剩余部分返回为r。
func parsePAXRecord(s string) (k, v, r string, err error) {
	// 大小字段在第一个空格处结束。
	nStr, rest, ok := strings.Cut(s, " ")
	if !ok {
		return "", "", s, ErrHeader
	}

	// 将第一个标记解析为十进制整数。
	n, perr := strconv.ParseInt(nStr, 10, 0) // 故意将其解析为原生int 
	if perr != nil || n < 5 || n > int64(len(s)) {
		return "", "", s, ErrHeader
	}
	n -= int64(len(nStr) + 1) // 从s中的索引转换为rest中的索引
	if n <= 0 {
		return "", "", s, ErrHeader
	}

	// 提取空格和最后换行符之间的所有内容。
	rec, nl, rem := rest[:n-1], rest[n-1:n], rest[n:]
	if nl != "\n" {
		return "", "", s, ErrHeader
	}

	// 第一个等于将键与值分开。
	k, v, ok = strings.Cut(rec, "=")
	if !ok {
		return "", "", s, ErrHeader
	}

	if !validPAXRecord(k, v) {
		return "", "", s, ErrHeader
	}
	return k, v, rem, nil
}

// formatPAXRecord格式化单个PAX记录，并在其前面加上适当长度的
// 前缀。
func formatPAXRecord(k, v string) (string, error) {
	if !validPAXRecord(k, v) {
		return "", ErrHeader
	}

	const padding = 3 // 为“”、“=”和“\n”添加额外填充
	size := len(k) + len(v) + padding
	size += len(strconv.Itoa(size))
	record := strconv.Itoa(size) + " " + k + "=" + v + "\n"

	// 如果添加大小字段会增加记录大小，则进行最终调整。
	if len(record) != size {
		size = len(record)
		record = strconv.Itoa(size) + " " + k + "=" + v + "\n"
	}
	return record, nil
}

// validPAXRecord报告每个
// 记录的格式为：
// /%d%s=%s\n“%（大小、键、值）
// 
// 键和值应为UTF-8，但坏作者的数量
// 迫使我们更自由。
// 因此，我们只拒绝带有NUL的所有键，并且只拒绝值
// 中的NUL，用于USTAR字符串字段的PAX版本。
// 密钥不能包含“=”字符。
func validPAXRecord(k, v string) bool {
	if k == "" || strings.Contains(k, "=") {
		return false
	}
	switch k {
	case paxPath, paxLinkpath, paxUname, paxGname:
		return !hasNUL(v)
	default:
		return !hasNUL(k)
	}
}
